Micron Document
NexusPi Git Node

Node / mirrors / RTNode-HeltecV4 / files / TcpInterface.h

Displaying Raw • Download

TcpInterface.h 760e92f186fb1467b5e2f8dbf13d568c31979a60 (760e92f1) Text, 16.29 KB

T8b949e// Copyright (C) 2026, Boundary Mode Extension
T8b949e// Based on microReticulum_Firmware by Mark Qvist
T8b949e//
T8b949e// TcpInterface — An RNS InterfaceImpl that bridges the WiFi TCP
T8b949e// connection as a second RNS transport interface, enabling
T8b949e// Boundary mode operation between LoRa and TCP/IP backbone.
T8b949e//
T8b949e// This program is free software: you can redistribute it and/or modify
T8b949e// it under the terms of the GNU General Public License as published by
T8b949e// the Free Software Foundation, either version 3 of the License, or
T8b949e// (at your option) any later version.

Tff7b72#Tff7b72ifndef TCP_INTERFACE_H
Tff7b72#Tff7b72define TCP_INTERFACE_H

Tff7b72#Tff7b72ifdef HAS_RNS
Tff7b72#Tff7b72ifdef BOUNDARY_MODE

Tff7b72#Tff7b72include T8b949e<WiFi.h>
Tff7b72#Tff7b72include T8b949e<Interface.h>
Tff7b72#Tff7b72include T8b949e<Transport.h>
Tff7b72#Tff7b72include T8b949e<Bytes.h>

T8b949e// ─── TCP Interface Configuration ─────────────────────────────────────────────
Tff7b72#Tff7b72define TCP_IF_DEFAULT_PORT 4242
Tff7b72#Tff7b72define TCP_IF_MAX_CLIENTS 4
Tff7b72#Tff7b72define TCP_IF_HW_MTU 1064
Tff7b72#Tff7b72define TCP_IF_CONNECT_TIMEOUT 6000 T8b949e// ms
Tff7b72#Tff7b72define TCP_IF_WRITE_TIMEOUT 2000 T8b949e// ms — short to avoid WDT
Tff7b72#Tff7b72define TCP_IF_READ_TIMEOUT 120000 T8b949e// ms — 2 minutes (backbone can go quiet)
Tff7b72#Tff7b72define TCP_IF_RECONNECT_MIN 10000 T8b949e// ms — initial reconnect interval
Tff7b72#Tff7b72define TCP_IF_RECONNECT_MAX 120000 T8b949e// ms — max backoff (2 minutes)
Tff7b72#Tff7b72define TCP_IF_KEEPALIVE_INTERVAL 30000 T8b949e// ms — send empty HDLC frames to keep link alive
Tff7b72#Tff7b72define TCP_IF_POLL_INTERVAL 10 T8b949e// ms

T8b949e// HDLC-like framing for TCP (matches Reticulum-rust tcp_interface)
Tff7b72#Tff7b72define HDLC_FLAG 0x7E
Tff7b72#Tff7b72define HDLC_ESC 0x7D
Tff7b72#Tff7b72define HDLC_ESC_MASK 0x20

T8b949e// ─── TCP Interface Mode ──────────────────────────────────────────────────────
Tff7b72enum Te6edf3TcpIfMode Tb4b4b4{
Te6edf3TCP_IF_MODE_SERVER Tff7b72= T79c0ff0Tb4b4b4, T8b949e// Listen for incoming connections (from backbone rnsd)
Te6edf3TCP_IF_MODE_CLIENT Tff7b72= T79c0ff1Tb4b4b4, T8b949e// Connect out to a backbone rnsd TCP server
Tb4b4b4}Tb4b4b4;

T8b949e// ─── Client connection state ─────────────────────────────────────────────────
Tff7b72struct T56d364TcpClient Tb4b4b4{
Te6edf3WiFiClient Te6edf3clientTb4b4b4;
Tffa657uint32_t Te6edf3last_activityTb4b4b4;
Tffa657bool Te6edf3activeTb4b4b4;
T8b949e// HDLC deframe state
Tffa657bool Te6edf3in_frameTb4b4b4;
Tffa657bool Te6edf3escapeTb4b4b4;
Tffa657uint8_t Te6edf3rxbufTb4b4b4[Te6edf3TCP_IF_HW_MTUTb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3rxlenTb4b4b4;
Tb4b4b4}Tb4b4b4;

T8b949e// ─── TcpInterface Class ─────────────────────────────────────────────────────
Te6edf3class Te6edf3TcpInterface Tff7b72: Te6edf3public Te6edf3RNSTff7b72:Tff7b72:Te6edf3InterfaceImpl Tb4b4b4{
Te6edf3publicTff7b72:
Te6edf3TcpInterfaceTb4b4b4(Te6edf3TcpIfMode Te6edf3modeTb4b4b4, Tffa657uint16_t Te6edf3port Tff7b72= Te6edf3TCP_IF_DEFAULT_PORTTb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3target_host Tff7b72= Tffa657nullptrTb4b4b4, Tffa657uint16_t Te6edf3target_port Tff7b72= T79c0ff0Tb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffTcpInterfaceTa5d6ff"Tb4b4b4)
Tff7b72: Te6edf3RNSTff7b72:Tff7b72:Te6edf3InterfaceImplTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4,
Te6edf3_modeTb4b4b4(Te6edf3modeTb4b4b4)Tb4b4b4,
Te6edf3_portTb4b4b4(Te6edf3portTb4b4b4)Tb4b4b4,
Te6edf3_target_portTb4b4b4(Te6edf3target_portTb4b4b4)Tb4b4b4,
Te6edf3_serverTb4b4b4(Tffa657nullptrTb4b4b4)Tb4b4b4,
Te6edf3_num_clientsTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_last_reconnectTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_last_keepaliveTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_reconnect_intervalTb4b4b4(Te6edf3TCP_IF_RECONNECT_MINTb4b4b4)Tb4b4b4,
Te6edf3_resolved_ipTb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_consecutive_failuresTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_startedTb4b4b4(Tffa657falseTb4b4b4)
Tb4b4b4{
Te6edf3_IN Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_OUT Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_HW_MTU Tff7b72= Te6edf3TCP_IF_HW_MTUTb4b4b4;
T8b949e// TCP links are effectively 10 Mbps+. Setting a realistic
T8b949e// bitrate lets Transport prefer TCP paths over LoRa when
T8b949e// both exist for the same destination.
T8b949e// announce_cap = 2% keeps backbone announce flooding in check.
Te6edf3_bitrate Tff7b72= T79c0ff10000000Tb4b4b4;
Te6edf3_announce_cap Tff7b72= T79c0ff2.0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3target_host Tff7b72!Tff7b72= Tffa657nullptrTb4b4b4) Tb4b4b4{
Te6edf3strncpyTb4b4b4(Te6edf3_target_hostTb4b4b4, Te6edf3target_hostTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3_target_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3_target_hostTb4b4b4[Tff7b72sizeofTb4b4b4(Te6edf3_target_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3_target_hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

Te6edf3virtual Tff7b72~Te6edf3TcpInterfaceTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Lifecycle ───────────────────────────────────────────────────────────
Tffa657bool Te6edf3startTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_startedTb4b4b4) Tff7b72return Tffa657trueTb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_SERVERTb4b4b4) Tb4b4b4{
Te6edf3_server Tff7b72= Te6edf3new Te6edf3WiFiServerTb4b4b4(Te6edf3_portTb4b4b4, Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4)Tb4b4b4;
Te6edf3_serverTff7b72-Tff7b72>Te6edf3beginTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_serverTff7b72-Tff7b72>Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Server listening on port %dTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_portTb4b4b4)Tb4b4b4;
Te6edf3_started Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Client mode — try initial connection
Te6edf3_started Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_connect_clientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72return Te6edf3_startedTb4b4b4;
Tb4b4b4}

Tffa657void Te6edf3stopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3_serverTb4b4b4) Tb4b4b4{
Te6edf3_serverTff7b72-Tff7b72>Te6edf3endTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3_serverTb4b4b4;
Te6edf3_server Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}
Te6edf3_started Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_num_clients Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Main loop — call from Arduino loop() ────────────────────────────────
Tffa657void Te6edf3loopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_startedTb4b4b4) Tff7b72returnTb4b4b4;

T8b949e// Accept new connections in server mode
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_SERVER Tff7b72&Tff7b72& Te6edf3_serverTb4b4b4) Tb4b4b4{
Te6edf3WiFiClient Te6edf3newClient Tff7b72= Te6edf3_serverTff7b72-Tff7b72>Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3newClientTb4b4b4) Tb4b4b4{
Te6edf3_accept_clientTb4b4b4(Te6edf3newClientTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

T8b949e// Client mode reconnection (with WiFi check + exponential backoff)
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_CLIENT Tff7b72&Tff7b72& Te6edf3_num_clients Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3now Tff7b72- Te6edf3_last_reconnect Tff7b72>Tff7b72= Te6edf3_reconnect_intervalTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3statusTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= Te6edf3WL_CONNECTEDTb4b4b4) Tb4b4b4{
Te6edf3_connect_clientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// WiFi not connected — skip TCP attempt, just update timer
Te6edf3_last_reconnect Tff7b72= Te6edf3nowTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// Send keepalive (empty HDLC frames) to prevent read timeout on both sides
Tff7b72if Tb4b4b4(Te6edf3_num_clients Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3now Tff7b72- Te6edf3_last_keepalive Tff7b72>Tff7b72= Te6edf3TCP_IF_KEEPALIVE_INTERVALTb4b4b4) Tb4b4b4{
Te6edf3_last_keepalive Tff7b72= Te6edf3nowTb4b4b4;
Tffa657uint8_t Te6edf3kaTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ Te6edf3HDLC_FLAGTb4b4b4, Te6edf3HDLC_FLAG Tb4b4b4}Tb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72&Tff7b72& Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3kaTb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// Process incoming data from all active clients
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tff7b72continueTb4b4b4;

Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d disconnectedTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3iTb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_num_clientsTff7b72-Tff7b72-Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}

T8b949e// Check read timeout
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72> T79c0ff0 Tff7b72&Tff7b72&
Tb4b4b4(Te6edf3millisTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activityTb4b4b4) Tff7b72> Te6edf3TCP_IF_READ_TIMEOUTTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d read timeoutTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3iTb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_num_clientsTff7b72-Tff7b72-Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}

T8b949e// Read available bytes and deframe
Tff7b72while Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3byte Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3readTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_hdlc_deframeTb4b4b4(Te6edf3iTb4b4b4, Te6edf3byteTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Stats ───────────────────────────────────────────────────────────────
Tffa657int Te6edf3clientCountTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_num_clientsTb4b4b4; Tb4b4b4}
Tffa657bool Te6edf3isStartedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_startedTb4b4b4; Tb4b4b4}
Tffa657bool Te6edf3isConnectedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_num_clients Tff7b72> T79c0ff0Tb4b4b4; Tb4b4b4}

Te6edf3protectedTff7b72:
T8b949e// ─── RNS InterfaceImpl: outgoing packet from RNS Transport ───────────────
Te6edf3virtual Tffa657void Te6edf3send_outgoingTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3dataTb4b4b4) Te6edf3override Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_started Tff7b72|Tff7b72| Te6edf3_num_clients Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4;

T8b949e// HDLC frame the data
Tffa657uint8_t Te6edf3frame_bufTb4b4b4[Te6edf3TCP_IF_HW_MTU Tff7b72* T79c0ff2 Tff7b72+ T79c0ff4Tb4b4b4]Tb4b4b4; T8b949e// worst case: every byte escaped + 2 flags
Tffa657uint16_t Te6edf3flen Tff7b72= T79c0ff0Tb4b4b4;

Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_FLAGTb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3b Tff7b72= Te6edf3dataTb4b4b4.Te6edf3dataTb4b4b4(Tb4b4b4)Tb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3b Tff7b72=Tff7b72= Te6edf3HDLC_FLAG Tff7b72|Tff7b72| Te6edf3b Tff7b72=Tff7b72= Te6edf3HDLC_ESCTb4b4b4) Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_ESCTb4b4b4;
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3b Tff7b72^ Te6edf3HDLC_ESC_MASKTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3bTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3flen Tff7b72>Tff7b72= Tff7b72sizeofTb4b4b4(Te6edf3frame_bufTb4b4b4) Tff7b72- T79c0ff4Tb4b4b4) Tff7b72breakTb4b4b4; T8b949e// safety
Tb4b4b4}
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_FLAGTb4b4b4;

T8b949e// Send to all connected clients (non-blocking: no flush)
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72&Tff7b72& Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tffa657size_t Te6edf3written Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3frame_bufTb4b4b4, Te6edf3flenTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3written Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Write failed on client %d, droppingTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3iTb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_num_clientsTff7b72-Tff7b72-Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3yieldTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// feed WDT between TCP writes and RNS processing

T8b949e// Post-send housekeeping
Te6edf3InterfaceImplTff7b72:Tff7b72:Te6edf3handle_outgoingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── RNS InterfaceImpl: incoming packet to RNS Transport ─────────────────
Te6edf3virtual Tffa657void Te6edf3handle_incomingTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3dataTb4b4b4) Te6edf3override Tb4b4b4{
Te6edf3TRACEFTb4b4b4(Ta5d6ff"Ta5d6ffTcpInterface.handle_incoming: (%u bytes)Ta5d6ff"Tb4b4b4, Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3InterfaceImplTff7b72:Tff7b72:Te6edf3handle_incomingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3privateTff7b72:
T8b949e// ─── HDLC byte-level deframing ──────────────────────────────────────────
Tffa657void Te6edf3_hdlc_deframeTb4b4b4(Tffa657int Te6edf3idxTb4b4b4, Tffa657uint8_t Te6edf3byteTb4b4b4) Tb4b4b4{
Te6edf3TcpClientTff7b72& Te6edf3c Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3idxTb4b4b4]Tb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_FLAGTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72&Tff7b72& Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// End of frame — deliver to RNS
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Td2a8ffdataTb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4, Te6edf3cTb4b4b4.Te6edf3rxlenTb4b4b4)Tb4b4b4;
Te6edf3handle_incomingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657trueTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frameTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3escapeTb4b4b4) Tb4b4b4{
Te6edf3byte Tff7b72^Tff7b72= Te6edf3HDLC_ESC_MASKTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_ESCTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Accept a new server-mode client ─────────────────────────────────────
Tffa657void Te6edf3_accept_clientTb4b4b4(Te6edf3WiFiClientTff7b72& Te6edf3newClientTb4b4b4) Tb4b4b4{
T8b949e// Find a free slot
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3newClientTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_WRITE_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_num_clientsTff7b72+Tff7b72+Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d connected from %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3iTb4b4b4, Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3remoteIPTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e// No free slots — reject
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Max clients reached, rejecting connectionTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3newClientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Client-mode outbound connection ─────────────────────────────────────
Tffa657void Te6edf3_connect_clientTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_target_hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] No target host configured for client modeTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}

Te6edf3WiFiClient Te6edf3clientTb4b4b4;
Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_CONNECT_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;

Tffa657bool Te6edf3connected Tff7b72= Tffa657falseTb4b4b4;

T8b949e// Try cached IP first (avoids DNS lookup on every reconnect)
Tff7b72if Tb4b4b4(Te6edf3_resolved_ip Tff7b72!Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connecting to %s:%d (cached IP)...Tffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Te6edf3connected Tff7b72= Te6edf3clientTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3_resolved_ipTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3connectedTb4b4b4) Tb4b4b4{
T8b949e// Cached IP failed — clear cache and try fresh DNS
Te6edf3_resolved_ip Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Cached IP failed, retrying with DNSTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

Tff7b72if Tb4b4b4(Tff7b72!Te6edf3connectedTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connecting to %s:%d (DNS)...Tffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Te6edf3IPAddress Te6edf3resolvedTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3hostByNameTb4b4b4(Te6edf3_target_hostTb4b4b4, Te6edf3resolvedTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_resolved_ip Tff7b72= Te6edf3resolvedTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Resolved %s -> %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3resolvedTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3connected Tff7b72= Te6edf3clientTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3resolvedTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] DNS failed for %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3connectedTb4b4b4) Tb4b4b4{
Te6edf3clientTb4b4b4.Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_WRITE_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3clientTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_num_clients Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3_consecutive_failures Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MINTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connected to backbone at %s:%dTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3_consecutive_failuresTff7b72+Tff7b72+Tb4b4b4;
T8b949e// Exponential backoff: 10s -> 20s -> 40s -> 80s -> 120s (max)
Te6edf3_reconnect_interval Tff7b72= Te6edf3_reconnect_interval Tff7b72* T79c0ff2Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3_reconnect_interval Tff7b72> Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4) Tb4b4b4{
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4;
Tb4b4b4}
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Failed to connect to %s:%d (attempt %d, next retry in %ds)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4, Te6edf3_consecutive_failuresTb4b4b4,
Te6edf3_reconnect_interval Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_last_reconnect Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Member variables ────────────────────────────────────────────────────
Te6edf3TcpIfMode Te6edf3_modeTb4b4b4;
Tffa657uint16_t Te6edf3_portTb4b4b4;
Tffa657char Te6edf3_target_hostTb4b4b4[T79c0ff64Tb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3_target_portTb4b4b4;
Te6edf3WiFiServerTff7b72* Te6edf3_serverTb4b4b4;
Te6edf3TcpClient Te6edf3_clientsTb4b4b4[Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4]Tb4b4b4;
Tffa657int Te6edf3_num_clientsTb4b4b4;
Tffa657uint32_t Te6edf3_last_reconnectTb4b4b4;
Tffa657uint32_t Te6edf3_last_keepaliveTb4b4b4;
Tffa657uint32_t Te6edf3_reconnect_intervalTb4b4b4;
Te6edf3IPAddress Te6edf3_resolved_ipTb4b4b4;
Tffa657uint16_t Te6edf3_consecutive_failuresTb4b4b4;
Tffa657bool Te6edf3_startedTb4b4b4;
Tb4b4b4}Tb4b4b4;

Tff7b72#Tff7b72endif T8b949e// BOUNDARY_MODE
Tff7b72#Tff7b72endif T8b949e// HAS_RNS
Tff7b72#Tff7b72endif T8b949e// TCP_INTERFACE_H

Served by rngit 1.5.0 - Generated in 0.04s